﻿using System.Collections.Generic;
using Godot.core.io;
using Godot.core.os;

namespace Godot.core
{
    public partial class InputMap : Object
    {
        private class __Action 
        {
            public int id;
            public LinkedList<InputEvent> inputs;

            public __Action()
            {
                inputs = new LinkedList<InputEvent>();
            }
        }

        public static InputMap singleton
        {
            get { return __singleton; }
        }

        private LinkedListNode<InputEvent> __find_event(ref LinkedList<InputEvent> p_list, InputEvent p_event)
        {
            for(LinkedListNode<InputEvent> E = p_list.First; E != null; E = E.Next) 
            {
		        InputEvent e = E.Value;
		        if(e.type  != p_event.type)
			        continue;

		        if (e.type != InputEvent.Type.KEY && e.device != p_event.device)
			        continue;

		        bool same = false;

		        switch(p_event.type) 
                {
			        case InputEvent.Type.KEY: {
				        same = e.key.scancode == p_event.key.scancode && e.key.mod == p_event.key.mod;
			        } break;

			        case InputEvent.Type.JOYSTICK_BUTTON: {
				        same = e.joy_button.button_index == p_event.joy_button.button_index;
			        } break;

			        case InputEvent.Type.MOUSE_BUTTON: {
				        same = e .mouse_button.button_index == p_event.mouse_button.button_index;
			        } break;

			        case InputEvent.Type.JOYSTICK_MOTION: {
				        same = e.joy_motion.axis == p_event.joy_motion.axis;
			        } break;
		        }

		        if (same)
			        return E;
	        }

	        return null;
        }

        public void add_action(StringName p_action) 
        {
	        if(__input_map.ContainsKey(p_action))
                return;
	        __input_map[p_action] = new __Action();	        
	        __input_map[p_action].id = __last_id;
	        __input_id_map[__last_id] = p_action;
	        __last_id++;
        }

        public void action_add_event(StringName p_action, InputEvent p_event) 
        {
	        if(p_event.type == InputEvent.Type.ACTION)
                return;

	        if(!__input_map.ContainsKey(p_action))
                return;

	        if (__find_event(ref __input_map[p_action].inputs, p_event) != null)
		        return; //already gots

	        __input_map[p_action].inputs.AddLast(p_event);
        }

        public void load_from_globals() 
        {
	        __input_map.Clear();

	        List<PropertyInfo> pinfo = new List<PropertyInfo>();
	        Globals.singleton.get_property_list(ref pinfo);

	        foreach(PropertyInfo E in pinfo) 
            {
		        PropertyInfo pi = E;

		        if (!pi.name.StartsWith("input/"))
			        continue;

		        string name = pi.name.Substring(pi.name.IndexOf("/") + 1, pi.name.Length);

		        add_action(name);
                bool valid = false;
		        List<Variant> va = Globals.singleton.get(pi.name, ref valid);

		        for(int i = 0; i < va.Count; i++) 
                {
			        InputEvent ie = va[i];
			        if (ie.type == InputEvent.Type.NONE)
				        continue;
			        action_add_event(name,ie);
		        }

	        }

        }

        public InputMap() 
        {
	        if(__singleton != null) return;
            __input_id_map = new Dictionary<int, StringName>();
            __input_map = new Dictionary<StringName, __Action>();
	        __singleton = this;
        }

        private Dictionary<StringName, __Action> __input_map;
        
        private Dictionary<int, StringName> __input_id_map;
        
        private static InputMap __singleton = null;
        
        private static int __last_id = 1;
    }

    public partial class InputMap
    {
        #region OBJ_TYPE( InputMap, Object )
        /* TYPE API */
        public override string get_type() { return "InputMap"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "InputMap") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property)
        {
            if (base._setv(p_name, p_property))
                return true;

            if (typeof(InputMap).GetMethod("_set") != typeof(Object).GetMethod("_set"))
            {
                return _set(p_name, p_property);
            }
            return false;
        }

        protected override bool _getv(StringName p_name, ref Variant r_property)
        {
            if (typeof(InputMap).GetMethod("_get") != typeof(Object).GetMethod("_get"))
            {
                if (_get(p_name, ref r_property))
                    return true;
            }

            return base._getv(p_name, ref r_property);
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed)
        {
            if (!p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }

            p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));

            if (!_is_gpl_reversed())
                ObjectTypeDB.get_property_list("InputMap", ref p_list, true);

            if (typeof(InputMap).GetMethod("_get_property_list") != typeof(Object).GetMethod("_get_property_list"))
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("InputMap", ref p_list, true);

            if (p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed)
        {
            if (!p_reversed)
                base._notificationv(p_notification, p_reversed);

            if (typeof(InputMap).GetMethod("_notification") != typeof(Object).GetMethod("_notification"))
            {
                _notification(p_notification);
            }

            if (p_reversed)
                base._notificationv(p_notification, p_reversed);
        }

        public static new string get_type_static() { return "InputMap"; }

        public static new string get_parent_type_static() { return Object.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list)
        {
            Object.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static()
        {
            string category = Object.get_category_static();
            if (typeof(InputMap).GetMethod("_get_category") != typeof(Object).GetMethod("_get_category"))
            {
                if (category != "")
                    category += "/";
                category += _get_category();
            }
            return category;
        }

        public static new string inherits_static() { return "Object"; }

        public static new void get_valid_parents_static(ref List<string> p_parents)
        {
            if (typeof(InputMap).GetMethod("_get_valid_parents_static") != typeof(Object).GetMethod("_get_valid_parents_static"))
            {
                InputMap._get_valid_parents_static(ref p_parents);
            }

            Object.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            Object.initialize_type();
            ObjectTypeDB._add_type<InputMap>();
            if (typeof(InputMap).GetMethod("_bind_methods") != typeof(Object).GetMethod("_bind_methods"))
                _bind_methods();
            __initialized = true;
        }

        private static bool __initialized = false;
        #endregion
    }
}
